package hypeerweb;

import gui.Main.GUICore;

import java.io.Serializable;
import java.util.HashSet;
import network.GlobalObjectId;
import network.LocalObjectId;
import network.NodeProxy;
import network.ObjectDB;
import network.PortNumber;

/**
 * The object model of the HyPeerWeb, implemented as a singleton Domain: nodes :
 * HashSet<Node>
 * 
 * @author Thomas
 */
public class HyPeerWebSegment implements Serializable {
	private static final long serialVersionUID = 2374553972915153404L;

	private final String objectDBName = "ObjectDatabase.db";

	/**
	 * The single instance of this class
	 */
	private static HyPeerWebSegment singleton;

	/**
	 * The set of all Nodes in the HyPeerWeb
	 */
	private HashSet<Node> nodes;

	/**
	 * The set of all connected GUIs
	 */
	private HashSet<GUICore> guis;
	
	/**
	 * The previous segment in the HyPeerWeb
	 */
	private HyPeerWebSegment prev;
	
	/**
	 * The next segment in the HyPeerWeb
	 */
	private HyPeerWebSegment next;

	/**
	 * The local object id of this HyPeerWeb segment
	 */
	private LocalObjectId localObjectId;

	/**
	 * The private constructor, used to generate the singleton.
	 * 
	 * @pre <i>None</i>
	 * @post nodes != null AND the HyPeerWeb is an accurate reflection of the
	 *       objects in the database
	 */
	private HyPeerWebSegment() {
		// System.out.println("HyPeerWeb()");
		nodes = new HashSet<Node>();
		guis = new HashSet<GUICore>();
		setNext(this);
		setPrev(this);
		localObjectId = new LocalObjectId();
		reload();
	}

	/**
	 * This is only for the HyPeerWebProxy to use
	 */
	protected HyPeerWebSegment(int iDontCare) {
	}

	/**
	 * Adds a GUI to the set of connected GUIs
	 * 
	 * @param gui
	 *            The GUICore to be added
	 */
	public void addGUI(GUICore gui) {
		guis.add(gui);
	}

	/**
	 * Adds a Node to the HyPeerWeb
	 * 
	 * @param node
	 *            The Node to be added
	 * @pre node != null
	 * @post node is a member of nodes
	 */
	public void addNode(Node node) {
		// System.out.println("addNode(" + node + ")");
		assert node != null;
		if (node == Node.NULL_NODE) {
			node = new Node(0);
		}
		nodes.add(node);
		if(ObjectDB.getSingleton().getValue(node.getLocalObjectId())==null)
		if (!node.isProxy()) {
			ObjectDB.getSingleton().store(node.getLocalObjectId(), node);
		}
	}

	/**
	 * Adds a new Node to the HyPeerWeb, using startNode to find the insertion
	 * point.
	 * 
	 * @param newNode
	 *            The Node to be added
	 * @param startNode
	 *            The Node that does the work
	 * @pre newNode != null startNode != null newNode != NULL_NODE newNode is
	 *      not already contained by the HyPeerWeb
	 * @post All previously existing Nodes still exist newNode is now a member
	 *       of the HyPeerWeb The HyPeerWeb is still valid, according to
	 *       constraints of a HyPeerWeb
	 */
	public void addToHyPeerWeb(int startId, Node newNode) {
		// System.out.println("addToHyPeerWeb(" + newNode + ", " + startNode +
		// ")");
		Node startNode = getNode(startId);
		if(newNode == null) {
			newNode = new Node(0);
		}
		ObjectDB.getSingleton().store(newNode.getLocalObjectId(), newNode);
		if (startNode != null) {
				startNode.addToHyPeerWeb(newNode);
		}
		if (nodes.size() > 0 || startNode == null) {
			addNode(newNode);
		}

		refreshGUIs();
	}

	/**
	 * Clears the HyPeerWeb
	 * 
	 * @pre none
	 * @post nodes.size() == 0
	 */
	public void clear() {
		// System.out.println("clear()");
		for (Node node : new HashSet<Node>(nodes)) {
			ObjectDB.getSingleton().remove(node.getLocalObjectId());
			nodes.remove(node);
		}
		addToHyPeerWeb(0, null);
	}

	/**
	 * Determines whether the indicated node is in the HyPeerWeb.
	 * 
	 * @param node
	 *            The indicated Node
	 * @return True if node is contained in nodes, false otherwise
	 * @pre node != null
	 * @post The correct value is returned
	 */
	public boolean contains(Node node) {
		// System.out.println("contains(" + node + ")");
		return nodes.contains(node);
	}

	/*
	 * static public void createHyPeerWebWith(int numberOfNodes) { //
	 * System.out.println("createHyPeerWebWith(" + numberOfNodes + ")");
	 * HyPeerWeb hypeerweb = HyPeerWeb.getSingleton(); hypeerweb.clear(); Node
	 * node0 = new Node(0); hypeerweb.addToHyPeerWeb(null);
	 * 
	 * for (int i = 1; i < numberOfNodes; i++) { hypeerweb.addToHyPeerWeb(0); }
	 * }
	 */
	/**
	 * Returns the single HyPeerWeb Database.
	 * 
	 * @return The singleton HyPeerWeb
	 * @pre none
	 * @post result == HyPeerWeb.singleton
	 */
	public HyPeerWebDatabase getHyPeerWebDatabase() {
		// System.out.println("getHyPeerWebDatabase()");
		return HyPeerWebDatabase.getSingleton();
	}

	/**
	 * Gets the node with the id i.
	 * 
	 * @param i
	 *            The id of the Node you are retrieving
	 * @return The Node whose id == i
	 * @pre i >= 0
	 * @post The Node whose id == i if it exists, otherwise null
	 */
	public Node getNode(int i) {
		// System.out.println("getNode(" + i + ")");
		assert i > -1;
		for (Node node : nodes) {
			if (node.getWebId().getValue() == i) {
				return node;
			}
		}
		return null;
	}

	/**
	 * Gets the single HyPeerWeb, if one doesn't exist then one is created.
	 * 
	 * @return HyPeerWeb.singleton
	 * @pre none
	 * @post HypeerWeb.singleton != null
	 */
	public static HyPeerWebSegment getSingleton() {
		// System.out.println("getSingleton()");
		if (singleton == null) {
			singleton = new HyPeerWebSegment();
			ObjectDB.setFileLocation(singleton.objectDBName);
			ObjectDB.getSingleton().store(singleton.getLocalObjectId(),
					singleton);
			// ObjectDB.getSingleton().save();
		}
		return singleton;
	}

	/**
	 * Reloads the HyPeerWeb from the database using the default database name.
	 * 
	 * @pre preconditions of reload(String dbName)
	 * @post postconditions of reload(String dbName)
	 * @author Jared
	 */
	public void reload() {
		// System.out.println("reload()");
		reload(null);
		saveToDatabase();
	}

	/**
	 * Reloads the HyPeerWeb from the database using the default dbName. If the
	 * dbName is null then the results are loaded from the default database.
	 * 
	 * @param dbName
	 *            The name of the database to be read, or null if the default
	 *            name is to be used
	 * @pre none
	 * @post The HyPeerWeb is an accurate representation of the contents of the
	 *       database entitled dbName, or the default database if dbname == null
	 * @author Jared
	 */
	public void reload(String dbName) {
		// System.out.println("reload(" + dbName + ")");
		nodes.clear();
		HyPeerWebDatabase.initHyPeerWebDatabase(dbName);

		Iterable<SimplifiedNodeDomain> simpNodes = HyPeerWebDatabase
				.getSingleton().getNodes();

		// Populate nodes
		for (SimplifiedNodeDomain simpNode : simpNodes) {
			nodes.add(new Node(new WebId(simpNode.getWebId(), simpNode
					.getHeight())));
		}

		// Establish associations
		for (SimplifiedNodeDomain simpNode : simpNodes) {
			Node node = getNode(simpNode.getWebId());
			node.setFold(getNode(simpNode.getFold()));
			node.setSurrogateFold(getNode(simpNode.getSurrogateFold()));

			for (int neighbor : simpNode.getNeighbors()) {
				node.addNeighbor(getNode(neighbor));
			}

			for (int downPointer : simpNode.getDownPointers()) {
				node.addDownPointer(getNode(downPointer));
			}

			for (int upPointer : simpNode.getUpPointers()) {
				node.addUpPointer(getNode(upPointer));
			}
		}

		refreshGUIs();
	}

	public void removeFromHyPeerWeb(int nodeID) {
		// System.out.println("removeFromHyPeerWeb(" + node + ")");
		Node node = getNode(nodeID);
		assert node != Node.NULL_NODE;
		node.removeFromHyPeerWeb();
		nodes.remove(node);
		ObjectDB.getSingleton().remove(node.getLocalObjectId());
		ObjectDB.getSingleton().save();

		refreshGUIs();
	}

	public boolean isProxy() {
		return false;
	}

	/**
	 * Saves all the nodes in the HyPeerWeb and their corresponding information
	 * to the database.
	 * 
	 * @pre none
	 * @post The database contains only nodes and information contained in the
	 *       HyPeerWeb
	 */
	public void saveToDatabase() {
		// System.out.println("saveToDatabase()");
		try {
			// Convert Node to SimplifiedNodeDomain
			HashSet<SimplifiedNodeDomain> simpNodes = new HashSet<SimplifiedNodeDomain>();

			for (Node n : nodes) {
				simpNodes.add(n.constructSimplifiedNodeDomain());
			}

			HyPeerWebDatabase.getSingleton().resetDatabase(simpNodes);
		} catch (Exception ex) {
			// System.out.println("Error saving to database: " +
			// ex.getMessage());
		}
	}

	/**
	 * Gets the number of Nodes in the HyPeerWeb
	 * 
	 * @return The number of Nodes in the HyPeerWeb
	 * @pre none
	 * @post result == nodes.size()
	 */
	public int getSize() {
		return nodes.size();
	}

	public void close() {
		// System.out.println("close()");
	}

	public HyPeerWebSegment getPrev() {
		return prev;
	}

	public void setPrev(HyPeerWebSegment prev) {
		this.prev = prev;
	}

	public HyPeerWebSegment getNext() {
		return next;
	}

	public void setNext(HyPeerWebSegment next) {
		this.next = next;
	}
	
	public LocalObjectId getLocalObjectId() {
		// System.out.println("getLocalObjectId()");
		return localObjectId;
	}

	/**
	 * Invokes refresh on all connected GUIs
	 */
	private void refreshGUIs() {
		for (GUICore gui : guis) {
			gui.refresh(nodes.size());
		}
	}
}